package com.tchepannou.limbe.service.impl;

import com.tchepannou.limbe.ServiceContext;
import com.tchepannou.limbe.service.RenderException;
import com.tchepannou.limbe.service.RenderService;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;

import javax.servlet.ServletContext;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;


/**
 * Implementation of {@link RenderService} based on <a href="http://velocity.apache.org">Jakarta Velocity</a>
 * 
 * @author herve
 */
public class VelocityRenderService
    extends AbstractService
    implements RenderService
{
    //-- Static Attribute
    private static final Logger LOG = Logger.getLogger (VelocityRenderService.class.getName());
    private static final String TEMPLATE_SUFFIX = ".vm";

    //-- Attribute
    private String _encoding = "UTF-8";
    private VelocityEngine _engine = new VelocityEngine ();

    //-- RenderService overrides
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public void render (String path, Map data, Writer writer)
        throws RenderException, IOException
    {
        if (!isInitialized ())
        {
            throw new IllegalStateException ("The service is not initialized. call init() first");
        }
        
        try
        {
            // Important: Clone the data to make sure that when the
            // template is rendered, the data map is not altered!
            Map cdata = new HashMap (data);

            // Render
            VelocityContext vc = new VelocityContext (cdata);
            String template = path + TEMPLATE_SUFFIX;
            _engine.mergeTemplate (template, _encoding, vc, writer);
        }
        catch ( ResourceNotFoundException ex )
        {
            throw new RenderException (ex.getMessage (), path, -1, ex);
        }
        catch ( ParseErrorException ex )
        {
            throw new RenderException ("Syntax error", path, ex.getLineNumber (), ex);
        }
        catch ( MethodInvocationException ex )
        {
            throw new RenderException ("Method invocation error", path, ex.getLineNumber (), ex);
        }
        catch ( Exception ex )
        {
            throw new RenderException ("Method invocation error", path, -1, ex);
        }
    }

    //-- Service override
    @Override
    public void init (ServiceContext serviceContext)
    {
        LOG.info ("Initializing");
        super.init (serviceContext);
        try
        {
            Properties p = loadVelocityProperties ();
            _engine = new VelocityEngine ();
            _engine.init (p);
            LOG.info ("Initialized");
        }
        catch ( Exception e )
        {
            throw new IllegalStateException ("Unable to initialize the Render service", e);
        }
    }

    @Override
    public void destroy ()
    {
        LOG.info("Destroying");
        
        super.destroy ();
        _engine = null;
        
        LOG.info("Destroyed");
    }


    private Properties loadVelocityProperties ()
        throws IOException
    {
        /* load the configuration */
        Properties p = null;
        try
        {
            /* Load configuration */
            p = loadConfigurationAsProperties ("velocity.properties");
        }
        catch (FileNotFoundException e)
        {
        }
        if (p == null)
        {
            InputStream in = getClass().getResourceAsStream("/velocity.properties");
            p = loadConfigurationAsProperties(in);            
        }
            
        /* set the root path */
        ServletContext context = getServiceContext ().getServletContext ();
        String rootDir = context.getRealPath ("/");
        p.setProperty ("file.resource.loader.path", rootDir);

        /* Root Path */
        LOG.info ("Velocity Configuration");
        for (Object name : p.keySet ())
        {
            LOG.info (" " + name + "=" + p.get (name));
        }
        
        return p;
    }
}
